package org.yuxuan.command.api;

import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.function.ToDoubleFunction;

import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;

import org.coherent.core.Command;
import static org.coherent.core.Command.*;
import org.coherent.core.Command.Parameter;
import static org.coherent.core.Command.Parameter.*;
import org.coherent.core.Command.Flow;
import static org.coherent.core.Command.Flow.*;
import org.coherent.core.Command.Body;
import static org.coherent.core.Command.Body.*;
import org.coherent.core.Parameters;
import static org.coherent.core.Parameters.*;

import static org.coherent.core.Command.Body.Notation.*;
import static org.coherent.core.Command.Flow.Notation.*;

import org.jparsec.core.Parser;
import static org.jparsec.core.Parser.*;
import org.jparsec.core.Parser.Message;
import static org.jparsec.core.Parser.Message.*;
import org.jparsec.core.Text;
import static org.jparsec.core.Text.*;

import static org.jparsec.core.Parser.Notation.*;

import org.monadium.core.data.Bottom;
import static org.monadium.core.data.Bottom.*;
import org.monadium.core.data.List;
import static org.monadium.core.data.List.*;

import static org.monadium.core.Notation.*;

public final class BukkitParameters {
	BukkitParameters() { }

	public static <C> Parameter<CommandSender, C, Player> parameterPlayer(String name, String description) {
		return suggest(extend(parameterPhrase(name, description), playerName -> $do(
			$(	getUser()														, context ->
			$(	simple(context.source().getServer().getPlayerExact(playerName))	, player ->
			$(	ensure(player != null, error("Player not found"))				, () ->
			$(	simple(player)													))))
			)), completer((input, context) -> list(context.source().getServer().getOnlinePlayers().stream()
				.map(player -> text(player.getName()))
				.toArray(Text[]::new)
			))
		);
	}

	public static <C> Parameter<CommandSender, C, World> parameterWorld(String name, String description) {
		return suggest(extend(parameterPhrase(name, description), worldName -> $do(
			$(	getUser()													, context ->
			$(	simple(context.source().getServer().getWorld(worldName))	, world ->
			$(	ensure(world != null, error("World not found"))				, () ->
			$(	simple(world)												))))
			)), completer((input, context) -> list(context.source().getServer().getWorlds().stream()
				.map(world -> text(world.getName()))
				.toArray(Text[]::new)
			))
		);
	}

	public static <C> Parameter<CommandSender, C, Material> parameterMaterial(String name, String description) {
		return suggest(extend(parameterPhrase(name, description), materialName -> $do(
			$(	getUser()													, context ->
			$(	simple(Arrays.stream(Material.values())
					.filter(material ->
						!material.isLegacy() &&
						material.getKey().toString().equals(materialName)
					)
					.findFirst()
				)															, material ->
			$(	ensure(material.isPresent(), error("Material not found"))	, () ->
			$(	simple(material.get())										))))
			)), completer((input, context) -> list(Arrays.stream(Material.values())
				.filter(material -> !material.isLegacy())
				.map(material -> text(material.getKey().toString()))
				.toArray(Text[]::new)
			))
		);
	}

	public static <C> Parameter<CommandSender, C, EntityType> parameterEntityType(String name, String description) {
		return suggest(extend(parameterPhrase(name, description), entityTypeName -> $do(
			$(	getUser()													, context ->
			$(	simple(Arrays.stream(EntityType.values())
					.filter(entityType ->
						entityType != EntityType.UNKNOWN &&
						entityType.getKey().toString().equals(entityTypeName)
					)
					.findFirst()
				)															, entityType ->
			$(	ensure(entityType.isPresent(), error("EntityType not found"))	, () ->
			$(	simple(entityType.get())										))))
			)), completer((input, context) -> list(Arrays.stream(EntityType.values())
				.filter(entityType -> entityType != EntityType.UNKNOWN)
				.map(entityType -> text(entityType.getKey().toString()))
				.toArray(Text[]::new)
			))
		);
	}

	public static <C> Parameter<CommandSender, C, Vector> parameterVector(String name, String description) {
		return nested(
			name,
			description,
			new Object() {
				public Parser<Text, Context<CommandSender, C>, Bottom, List<Completion>> componentCompleter(ToDoubleFunction<Location> extractor) {
					return completer((input, context) -> context.source() instanceof Entity
						? singleton(text(new DecimalFormat("0.00").format(extractor.applyAsDouble(((Entity) context.source()).getLocation()))))
						: nil()
					);
				}
				public <T> Body<CommandSender, C, T, Flow<T, Vector>> vectorBody() {
					return $do(
					$(	define(suggest(parameterDouble("x", "X component"), componentCompleter(Location::getX)))	, px ->
					$(	define(suggest(parameterDouble("y", "Y component"), componentCompleter(Location::getY)))	, py ->
					$(	define(suggest(parameterDouble("z", "Z component"), componentCompleter(Location::getZ)))	, pz ->
					$(	evaluate($do(
						$(	px	, x ->
						$(	py	, y ->
						$(	pz	, z ->
						$(	value(new Vector(x, y, z))	))))
						))																							))))
					);
				}
			}::vectorBody
		);
	}
}
